package jaci.openrio.toast.core.security; import jaci.openrio.toast.core.ToastBootstrap; import java.io.FilePermission; import java.net.SocketPermission; import java.security.Permission; import java.util.regex.Pattern; /** * The Toast Security Manager. The aim of this class is to notify Users when a Module is attempting to access something that * is not recommended. "Not Recommended" means things like deleting/writing/executing files that are not present in the * toast/ directory defined by the {@link ToastBootstrap}. Users can define in the Configuration file if they want to be * Loose, Strict or just not take notice of these kind of non-recommended behaviour. Loose will only warn the user in the * console without blocking the action, Strict will block the action completely, and None will not warn you at all. * * @author Jaci */ public class ToastSecurityManager extends SecurityManager { public static ToastSecurityManager INSTANCE; private static ThreadGroup group; private static ThreadGroup main; /** * Initialize the security manager. This is done by Toast during runtime and should NEVER be called by a module. */ public static void init() { if (SecurityPolicy.get() != SecurityPolicy.NONE) { INSTANCE = new ToastSecurityManager(); System.setSecurityManager(INSTANCE); main = Thread.currentThread().getThreadGroup(); group = new ThreadGroup(Thread.currentThread().getThreadGroup(), "Toasted"); } } /** * Check a permission. This performs the check statement for the permission type and handles it accordingly */ @Override public void checkPermission(Permission perm) { if (perm instanceof FilePermission) { } else if (perm instanceof SocketPermission) { SocketPermission sp = (SocketPermission) perm; h_Socket(sp); } else if (perm instanceof RuntimePermission) { RuntimePermission rp = (RuntimePermission) perm; h_Runtime(rp); } } /** * Returns the location of a given class or package in a callstack, or -1 if it is not present. */ public int existsInCallStack(String packageorclass, String method) { Pattern porc = Pattern.compile(packageorclass); Pattern meth = Pattern.compile(method); StackTraceElement[] elements = Thread.currentThread().getStackTrace(); for (int i = 0; i < elements.length; i++) { if (porc.matcher(elements[i].getClassName()).matches() && meth.matcher(elements[i].getMethodName()).matches()) return i; } return -1; } /** RUNTIME PERMISSIONS **/ /** * Checks for Runtime violations. This includes calling System.exit() from somewhere OTHER than Toast.shutdownSafely(). * This exists due to the nature of the Toast shutdownSafely() hook, as it is designed to stop all systems before * shutdown, to ensure issues do not arise. */ public void h_Runtime(RuntimePermission perm) { if (perm.getName().contains("exitVM")) { if (existsInCallStack("jaci.openrio.toast.core.Toast", "shutdown.*") == -1) { SecurityPolicy.log().warn("Unregistered System.exit() call - This should be called with Toast.shutdownSafely()!"); for (int i = 5; i <= 7; i++) SecurityPolicy.log().warn("\tat " + String.valueOf(Thread.currentThread().getStackTrace()[i])); } } } /** SOCKET PERMISSIONS **/ /* Disclaimer: We don't block the sockets, we just warn the user when it's not on a port that FMS can forward, so they don't pull their hair out at competition * when their sockets perms are disallowed */ private String[] exceptionPorts = new String[] { "1735", //NetworkTables "0", //Not yet bound / multicast "1110", "1150" // Driver Station }; /** * Handles the given SocketPermission. This serves to warn the user if a socket is using a port that FMS cannot forward, but will never * block a socket. */ public void h_Socket(SocketPermission perm) { if (Thread.currentThread().getThreadGroup().equals(main) && !isPortException(perm)) { throw new ThreadingException("Networking On Main Thread!"); } if (perm.getActions().contains("listen")) { if (existsInCallStack("sun.management.jmxremote.*", ".*") != -1) return; //RMI Profiler Routing SocketPermission impliee = new SocketPermission("*:5800-5810", "listen"); if (!impliee.implies(perm) && !isPortException(perm)) { switch (SecurityPolicy.get()) { case NONE: break; default: SecurityPolicy.log().warn("Socket Port Warning, not within 5800...5810 (FMS will not forward it!): " + perm.getName()); SecurityPolicy.log().exception(new Throwable()); break; } } } } /** * Checks if the port is an exception to the 5800-5810 rule (such as NetworkTables) */ private boolean portExceptionCheck(SocketPermission permission, String p) { SocketPermission impliee = new SocketPermission("*:" + p, "listen"); return impliee.implies(permission); } /** * Does the {@link #portExceptionCheck(SocketPermission, String)} on all exceptionPorts listed in the array. */ private boolean isPortException(SocketPermission perm) { for (String p : exceptionPorts) if (portExceptionCheck(perm, p)) return true; return false; } /** * Make sure new Threads are created on the ToastThreadPool group, to ensure * that Networking and other actions don't occur within the main Thread Group. */ public ThreadGroup getThreadGroup() { return group; } /** * Thrown when code violates a rule regarding Multithreading. For example, Networking SHOULD NOT be done on the * main Thread, but instead delegated elsewhere. If networking does occur on the main Thread, this exception * is thrown and the robot program stopped. */ public static class ThreadingException extends RuntimeException { public ThreadingException(String s) { super(s); } } }